package cn.jh.common.exception;

import java.util.HashSet;
import java.util.Set;

import static com.google.common.base.MoreObjects.firstNonNull;

/**
 * @author Yichuan
 * @version 1.0
 */
public class EntityException extends ErrorCodedException {

    private static final long serialVersionUID = 2944555725306962814L;

    /**
     * The default entity error code.
     */
    protected static final int ENTITY_CODE = 1500;
    /**
     * The default entity error message code.
     */
    protected static final String ENTITY_ERROR_CODE = "errors.entity.exception";

    /**
     * The default error message (XxxException simple name)
     */
    protected static final String ENTITY_ERROR_MESSAGE = EntityException.class.getSimpleName();

    /**
     * This exception bound fields.
     */
    private Set<String> fields;

    /**
     * Constructs a new entity exception with {@code null} as its
     * detail message.  The cause is not initialized, and may subsequently be
     * initialized by a call to {@link #initCause}.
     */
    public EntityException() {
        super(ENTITY_CODE, ENTITY_ERROR_CODE, ENTITY_ERROR_MESSAGE);
    }

    /**
     * Constructs a new entity exception with the specified {@code code}.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * @param code the exception code.
     */
    public EntityException(Integer code) {
        super(firstNonNull(code, ENTITY_CODE),
                ENTITY_ERROR_CODE, ENTITY_ERROR_MESSAGE);
    }

    /**
     * Constructs a new entity exception with the specified detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * @param message the detail message. The detail message is saved for
     *                later retrieval by the {@link #getMessage()} method.
     */
    public EntityException(String message) {
        this(ENTITY_CODE, message);
    }

    /**
     * Constructs a new entity exception with the specified detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * @param code    the exception code.
     * @param message the detail message. The detail message is saved for
     *                later retrieval by the {@link #getMessage()} method.
     */
    public EntityException(Integer code, String message) {
        super(firstNonNull(code, ENTITY_CODE),
                resolveErrorCode(message, ENTITY_ERROR_CODE),
                firstNonNull(message, ENTITY_ERROR_MESSAGE));
    }

    public EntityException(int code, String errorCode, String errorMessage) {
        super(code, errorCode, errorMessage);
    }

    /**
     * Constructs a new {@code EntityException} with given {@code ErrorCoded}.
     *
     * @param coded the given {@code ErrorCoded}.
     * @since 2.0
     */
    public EntityException(ErrorCoded coded) {
        super(firstNonNull(coded.getCode(), ENTITY_CODE),
                resolveErrorCode(coded.getErrorCode(), ENTITY_ERROR_CODE),
                firstNonNull(coded.getErrorMessage(), ENTITY_ERROR_MESSAGE));
    }

    public EntityException(Throwable cause) {
        super(cause);
    }

    /**
     * Check whether this exception contains an exception of the given type:
     * either it is of the given class itself or it contains a nested cause
     * of the given type.
     * @param exType the exception type to look for
     * @return whether there is a nested exception of the specified type
     */
    public boolean contains(Class<?> exType) {
        if (exType == null) {
            return false;
        }
        if (exType.isInstance(this)) {
            return true;
        }
        Throwable cause = getCause();
        if (cause == this) {
            return false;
        }
        if (cause instanceof EntityException) {
            return ((EntityException) cause).contains(exType);
        }
        else {
            while (cause != null) {
                if (exType.isInstance(cause)) {
                    return true;
                }
                if (cause.getCause() == cause) {
                    break;
                }
                cause = cause.getCause();
            }
            return false;
        }
    }

    public Set<String> getFields() {
        return fields;
    }

    @SuppressWarnings("unchecked")
    public <T extends EntityException> T addField(String field) {
        if (fields == null) {
            fields = new HashSet<>();
        }
        fields.add(field);
        return (T) this;
    }
}
